﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Gunningine
{
    public class GameStatePlay : GameState
    {
        private Map map = null;

        private Camera camera = null;

        private Camera debugCamera = null;

        private bool debugCameraMode = false;

        protected WorldState worldState = null;

        private Battery battery = null;

        /// <summary>
        /// These variables determine the camera position.  The height above the
        /// ground, and the  distance from the object it is viewing.
        /// The camera always views an object from behind it.
        /// </summary>
        protected float cameraHeight, cameraBehind, cameraAhead;

        /// <summary>
        /// The point in screen space that we are aiming at.
        /// </summary>
        protected Vector2 aimingTarget;

        /// <summary>
        /// Player entity.
        /// </summary>
        public Entity Player { get; private set; }

        /// <summary>
        /// The point in the world the player is pointing at.
        /// </summary>
        public Vector3 WorldAimingTarget { get; private set; }

        /// <summary>
        /// Constructor.
        /// </summary>
        public GameStatePlay() :
            base("Play")
        {
            this.cameraHeight = 80.0f;
            this.cameraBehind = 10.0f;
            this.cameraAhead = 1.0f;
        }

        #region GameState implementation

        /// <summary>
        /// Create the world state.
        /// </summary>
        /// <param name="prevState"></param>
        protected override void EnterImpl(GameState prevState)
        {
            this.worldState = new WorldState();
            this.battery = new Battery(512);
        }

        /// <summary>
        /// Create a new map.
        /// </summary>
        /// <param name="tilesX"></param>
        /// <param name="tilesY"></param>
        /// <param name="scale"></param>
        public Map CreateMap(int tilesX, int tilesY, float scale)
        {
            this.map = new Map(tilesX, tilesY, scale, this.scriptMgr, this.resourceMgr, this.worldState, this.battery);
            return this.map;
        }

        /// <summary>
        /// Create the player entity.
        /// </summary>
        /// <param name="type"></param>
        /// <param name="position"></param>
        /// <param name="direction"></param>
        public void CreatePlayer(string type, Vector3 position, Vector3 direction)
        {
            this.Player = CreateEntity(type, position, direction, 0);
            this.map.SetEntityPlayerControlled(this.Player, true);
        }

        /// <summary>
        /// Create an entity.
        /// </summary>
        /// <param name="type"></param>
        /// <param name="position"></param>
        /// <param name="direction"></param>
        /// <returns></returns>
        public Entity CreateEntity(string type, Vector3 position, Vector3 direction, int alignment)
        {
            return this.map.CreateEntity(type, position, direction, alignment);
        }

        /// <summary>
        /// Create camera.
        /// </summary>
        /// <param name="position"></param>
        /// <param name="direction"></param>
        /// <param name="up"></param>
        /// <returns></returns>
        public void CreateCamera(Vector3 position, Vector3 direction, Vector3 up)
        {
            this.camera = new Camera(position, direction, up);
            this.debugCamera = new Camera(position, direction, up);
        }

        /// <summary>
        /// Update the camera with its view settings.
        /// </summary>
        protected void UpdateCamera(Vector3 playerAimingDir)
        {
            this.camera.Position = this.Player.Position + Vector3.UnitZ * this.cameraHeight + Vector3.UnitY * this.cameraBehind;
                
            Vector3 target = this.Player.Position;

            float targetProj = playerAimingDir.Length > this.cameraAhead ? this.cameraAhead : playerAimingDir.Length;

            playerAimingDir.Normalise();
            target.X += playerAimingDir.X * targetProj;
            target.Y += playerAimingDir.Y * targetProj;

            this.camera.Direction = (target - this.camera.Position).Normalised();
            this.camera.Up = Vector3.CrossProduct(Vector3.UnitX, this.camera.Direction);

            // Update debug camera.
            if (!this.debugCameraMode)
            {
                this.debugCamera.Position = this.camera.Position;
                this.debugCamera.Direction = this.camera.Direction;
                this.debugCamera.Up = this.camera.Up;
            }
        }

        /// <summary>
        /// Exit this state.
        /// </summary>
        protected override void ExitImpl()
        {
            // Dispose of entities.
            this.map.RemoveAllEntities();
        }

        /// <summary>
        /// Toggle debug camera
        /// </summary>
        protected void ToggleDebugCamera()
        {
            this.debugCameraMode = !this.debugCameraMode;
        }

        /// <summary>
        /// Calculate the aiming target.
        /// </summary>
        protected void CalculateAimingTarget(int mouseX, int mouseY)
        {
            // First off, project player's world position to the screen.
            Vector2 playerPosScreen = this.renderSystem.Project(this.Player.Position, this.camera);

            // Then calculate the screen-space delta from mouse to player's screen position.
            Vector2 mousePosScreen = new Vector2(mouseX, mouseY);
            Vector2 aimDirScreen = (mousePosScreen - playerPosScreen).Normalised();

            // Transform this vector to 3d by projecting it from the player's world position,
            // using the camera's up vector as reference.  We use up because it is less likely to have
            // a zero z-component (this would only happen if the camera view was parallel to the ground,
            // which is never going to happen).
            Vector3 aimProjWorld = this.camera.Up;
            aimProjWorld.Z = 0.0f;
            aimProjWorld.Normalise();

            // Now we have the aiming direction, update the camera with it.
            Vector3 aimDirWorld = new Vector3(aimDirScreen.X, aimDirScreen.Y, 0.0f);
            this.WorldAimingTarget = this.Player.Position + aimDirWorld;
        }

        /// <summary>
        /// Run logic for playing state.
        /// </summary>
        /// <param name="frameTime">time since last update, in seconds.</param>
        /// <param name="inputMgr">input manager to query for input.</param>
        protected override void UpdateImpl(float frameTime, InputManager inputMgr)
        {
            // Set worldstate frametime for entities to use
            this.worldState.FrameTime = frameTime;

            // Camera positioning
            if (this.debugCameraMode)
            {
                if (inputMgr.IsKeyDown(KeyCode.A))
                    this.debugCamera.pan(-15.0f * frameTime, 0);
                if (inputMgr.IsKeyDown(KeyCode.D))
                    this.debugCamera.pan(15.0f * frameTime, 0);
                if (inputMgr.IsKeyDown(KeyCode.W))
                    this.debugCamera.travel(15.0f * frameTime);
                if (inputMgr.IsKeyDown(KeyCode.S))
                    this.debugCamera.travel(-15.0f * frameTime);
            }
            else
            {
                if (inputMgr.IsKeyDown(KeyCode.A))
                    this.cameraHeight += 15.0f * frameTime;
                if (inputMgr.IsKeyDown(KeyCode.Z))
                    this.cameraHeight -= 15.0f * frameTime;
                if (inputMgr.IsKeyDown(KeyCode.S))
                    this.cameraBehind -= 15.0f * frameTime;
                if (inputMgr.IsKeyDown(KeyCode.X))
                    this.cameraBehind += 15.0f * frameTime;
                if (inputMgr.IsKeyDown(KeyCode.D))
                    this.cameraAhead += 15.0f * frameTime;
                if (inputMgr.IsKeyDown(KeyCode.C))
                    this.cameraAhead -= 15.0f * frameTime;
            }

            // Update map, and the entities in it.
            this.map.Update();
            this.battery.Update(frameTime, null);
        }

        /// <summary>
        /// Render the game.
        /// </summary>
        /// <param name="renderSystem">render system to use for rendering.</param>
        protected override void RenderImpl(RenderSystem renderSystem)
        {
            renderSystem.Set3dProjection(90.0f);
            renderSystem.SetCamera(this.debugCameraMode ? this.debugCamera : this.camera);

            // Render map
            int tilesRendered = this.map.RenderWorld(renderSystem, this.camera, this.aimingTarget);
            int totalTiles = this.map.TilesX * this.map.TilesY;
            AddDebugText(String.Format("Tiles rendered: {0}/{1}", tilesRendered, totalTiles));

            // Render entities
            int entitiesRendered = this.map.RenderEntities(renderSystem, this.camera, this.aimingTarget);
            int totalEntities = this.map.Entities.Count;
            AddDebugText(String.Format("Entities rendered: {0}/{1}", entitiesRendered, totalEntities));

            // Render batteries
            this.battery.Render(renderSystem);
        }

        #endregion
    }
}